#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <getopt.h>
#include "parser.h"
#include "dir_explorer.h"
#include "utilidades.h"
#include "vector_dinamico.h"
#include "termino_merge.h"
#include "buffer_merge.h"
#include "merge.h"
#include "matriz_svdlibc.h"
#include "svdlib.h"
#include "svdutil.h"


/* ***************************************************************** *
 * 	          DEFINICION DE LAS OPCIONES CORTAS                  *
 * ***************************************************************** */

#define DIR 'd'
#define DIM 'n'
#define TERM 'p'
#define EXT 'e'
#define HELP 'h'
#define STOPWORDS 's'

/* ***************************************************************** *
 *      	DEFINICION DE LAS CONSTANTES DEL PROGRAMA            *
 * ***************************************************************** */

#define INDEXFILE "./file-id"
#define MERGEFILE "./output-merge"
#define MATRIXFILE "./output-matriz"
#define COLUMNAS_LOG "./columnas.log"
#define FILAS_LOG "./filas.log"
#define ELEMENTOS_LOG "./elementos.log"
#define SV "-S"
#define UT "-Ut"
#define VT "-Vt"
#define UxS "-UxS"
#define READFORMAT SVD_F_DB
#define WRITEFORMAT SVD_F_DB



/* ********************************************************************************************** *
 *      		     FUNCIONES AUXILIARES DEL PROGRAMA PRINCIPAL                          *
 * ********************************************************************************************** */

// Imprime por pantalla un aviso de error fatal.
void fatal_error(char* msj){

	fprintf(stderr,"ERROR:%s \nEl programa se cerrara...\n",msj);
	exit(-1);

}

void print_warning(char* msj){
	char c = 0;
	do{
		fprintf (stderr,"WARNING: %s. Desea continuar (S/N): ",msj);
		c = getchar();
		getchar();
	}while (c != 's' && c != 'n' && c != 'S' && c != 'N');
	
	if (c == 'n' || c == 'N'){
		fprintf(stderr,"El programa se cerrara...\n");
		exit(1);
	}
	printf("\n");
}


// Funcion que escribe la ruta del documento en el archivo de documentos.
void ruta_a_archivo(FILE* ruta_archivos,char* ruta){

	if( !ruta_archivos ) return;

	fprintf(ruta_archivos,"%s\n",ruta);

}


/* Obtiene el valor registrado en el archivo recibido por parametro.
 * Si el archivo no existe devuelve una cantidad negativa. Si existe
 * devuelve el valor extraido. */
int log_extraer(char* logfile){
	
	FILE* log_file = fopen(logfile,"r");
	int cantidad = -1;
	if( !log_file ) return cantidad;
	char* str_cantidad = leer_linea(log_file);
	cantidad = atoi(str_cantidad);
	free(str_cantidad);
	fclose(log_file);
	return cantidad;
	
}


// Registra un valor en un archivo log recibido por parametro.
void log_registrar(char* logfile,int cantidad){
	
	FILE* log_file = fopen(logfile,"w");
	fprintf(log_file,"%d\n",cantidad);
	fclose(log_file);
	
}


// Impresion por pantalla del comando --help.
void print_help(char* prog_name){

	printf("Modo de empleo: %s -d DIRECTORIO -n DIMENSIONES [OPCIONES]...\n",prog_name);
	printf("Indexa, para su futura busqueda, los archivos de un directorio\n");
	printf("\nLos argumentos obligatorios para las opciones largas son también obligatorios para las opciones cortas.\n");
	printf("-n | --dimension=<num> 			Dimension de la DVS reducida sobre la cual se realizaran busquedas. Por default 100\n");
	printf("-d | --directorio=<dir>			Selecciona el directorio a indexar. Por defecto, el directorio en el que se ejecuta el programa\n");
	printf("-p | --palabras-x-termino=<num>		Establece la cantidad de palabras que pueden formar un termino de indexacion. Por defecto, se establece en 3 palabras\n");
	printf("-e | --extension=<ext> 			Agrega una extension, para que se tengan en cuenta en la indexacion. Por defecto, la unica agregada es txt. (Se deben agregar extensiones sin punto)\n");
	printf("-h | --help				Muestra el mensaje de ayuda del programa\n");
	printf("-s | --stopwords			Deshabilita el filtro de stopwords, que esta habilitado por defecto. ATENCION: este es un modo experimenta. Las busquedas realizadas en este modo podrian devolver resultados no deseados.\n");
	printf("\nDesarrollado por Gonzalo Beviglia, Lucas Simonelli y Tomás Boccardo.\nOrganizacion de Datos 75.06, 2do Cuatrimestre 2012\n");
	exit(0);
}




/* ********************************************************************************************** *
 *      			FUNCIONES PRINCIPALES DEL PROGRAMA                                *
 * ********************************************************************************************** */


/* Extrae las opciones recibidas al ejecutar el programa y guarda alguna de las
 * posible variables en los punteros recibidos por parametro. Se definen las
 * opciones del programa en el formato especificado por getopt long. */
bool extraer_opciones(int argc, char** argv, char** dir, int* dim ,filtro_archivos_t* filtro, int* term, int* stopwords) {

	int option_index = 0;

	/* struct para el uso interno del getopt long */
	static struct option long_options[] = {
		
			{ "directorio", required_argument, NULL, DIR },
			{ "dimension", required_argument, NULL, DIM },
			{ "palabras-x-termino", required_argument, NULL, TERM },
			{ "extension", required_argument, NULL, EXT },
			{ "stopwords", no_argument, NULL, STOPWORDS },
			{ "help",no_argument, NULL, HELP },
			{ 0, 0, 0, 0 }
			
	};

	int option = 0;

	/* Mientras getopt_long lea parametros... */
	while ((option = getopt_long(argc, argv, "d:n:p:e:h:s", long_options, &option_index)) != -1) {

		/* Veo que parametro corresponde y cambio los valores de las variables correspondientes */
		switch (option) {
			case DIR:
				*dir = optarg;
				break;
			case DIM:
				*dim = atoi(optarg);
				break;
			case EXT:
				filtro_archivos_agregar_extension(filtro, optarg);
				break;
			case TERM:
				*term = atoi(optarg);
				break;
			case STOPWORDS:
				*stopwords = 1;
				break;
			case HELP:
				print_help(argv[0]);
				break;
			default:
				printf("Opcion no reconocida\n");
				print_help(argv[0]);
				break;
	}

	}

	return true;

}


/* Parsea el directorio recibido, parseando unicamente los archivos que
 * pasen el filtro recibido. Tambien recibe la longitud maxima (en palabras)
 * que puede tener un termino (n_gramas). Devuelve la cantidad de archivos
 * parseados. */
int parsear_directorio(int n_gramas, char* dir, vector_t* filtrados, int cant_archivos,int stopwords) {

	/* Abro el archivo de documentos a indexar */
	FILE* ruta_archivos = fopen(INDEXFILE, "w");


	int ID = 0;

	for (int i = 0; i < cant_archivos; i++) {

		// Siguiente archivo a parsear
		char* archivo = (char*) vector_obtener(filtrados, i);
		if (!archivo)
			continue;

		// Parseo el archivo
		printf("Parseando: %s\n", archivo);

		ID = parsear_archivo(archivo, 1024 * 1024, ID, n_gramas,stopwords);

		// Lo escribo en el indice
		ruta_a_archivo(ruta_archivos, archivo);

	}

	fclose(ruta_archivos);

	vector_destruir_con_elementos(filtrados, free, cant_archivos);

	return cant_archivos;

}


/* Genera el archivo de la matriz a partir de la cantidad de filas, columnas
 * y elementos distintos de cero recibidos. */
void generar_archivo_matriz(int filas, int columnas, int elementos_distintos_de_0) {

	FILE* input = fopen(MERGEFILE, "r");
	FILE* output = fopen(MATRIXFILE, "w");

	construir_matriz_svdlibc(input, output, filas, columnas,elementos_distintos_de_0);
	printf("Matriz final: \"%s\"\n\n", MATRIXFILE);

	fclose(input);
	fclose(output);

}




/* Escribe la matriz densa de la libreria SVDLIBC en disco, pero realizando
 * la transposicion en donde D[i][j] = Dt[j][i]. El formato de escritura es
 * SVD_F_DB (biario). */
void svdWriteDenseMatrixTranspose(DMat D , char* filename){

	FILE* file = fopen(filename,"wb");
	

	int i, j; 
	svd_writeBinInt(file, (int) D->cols); 
	svd_writeBinInt(file, (int) D->rows); 
	for (i = 0; i < D->cols; i++) 
		for (j = 0; j < D->rows; j++)  
			svd_writeBinFloat(file, (float) D->value[j][i]); 


	fclose(file);

}


// Realiza la multiplicacion de las matrices U y S^-1 (S^-1[i] = 1/S[i]). 
void multiplicar_UxSinv(DMat U, double* S, int dim){

	int rows = U->cols;

	for (int i = 0 ; i < rows ; i++){
		for (int j = 0; j < dim ; j++){
			U->value[j][i] = U->value[j][i] / S[j];
		}

	}

	svdWriteDenseMatrixTranspose(U,UxS);

}


/* Calcula la DVS de la matriz output-matriz mediante funciones de la 
 * libreria SVDLIBC. Recibe la cantidad de dimensiones con la que se quiere obtener la DVS. */
void calculo_dvs(int dim) {
	
	int iter = 0;
	double las2end[2] = {-1.0e-30, 1.0e-30};
	double k = 1e-6;
	
	printf("\nCargando matriz para realizar la DVS\n");
	SMat S = svdLoadSparseMatrix(MATRIXFILE,SVD_F_ST);
	SMat A = svdTransposeS(S);
	svdFreeSMat(S);

	SVDRec R;
	printf("Calculando DVS, por favor espere. Este proceso puede tardar varios minutos\n\n");
	if (! (R = svdLAS2(A,dim, iter, las2end, k)))	
		fatal_error("No se ha podido realizar la DVS");

	printf("\nOperacion finalizada con exito. Guardando matriz V\n");
	svdWriteDenseMatrixTranspose(R->Vt,VT);
	svdFreeDMat(R->Vt);
	R->Vt = NULL;
	
	printf("Calculando matriz UxS^-1.\n");
	multiplicar_UxSinv(R->Ut, R->S , R->d);
	svdFreeSVDRec(R);
	
	printf("\nEl proceso ha terminado con exito\n\n");

}

/* ********************************************************************************************** *
 *      				PROGRAMA PRINCIPAL                                        *
 * ********************************************************************************************** */

int main (int argc,char** argv){

	/* Variables para guardar los resultados, incializadas en la manera
	 * que se comportan por default. */
	char* dir = "./";
	int dim = 100;
	int n_gramas = 3;
	int stopwords = 0;
	buffer_t* buffer_merge;
	
	/* Recupero la cantidad de filas, columnas y elementos distintos
	 * de cero de parseos y merges anteriores. */
	int columnas = log_extraer(COLUMNAS_LOG);
	int filas = log_extraer(FILAS_LOG);
	int elementos_distintos_de_0 = log_extraer(ELEMENTOS_LOG);
	
	/* Se filtran los archivos a parsear. */
	filtro_archivos_t* filtro = filtro_archivos_crear();
	filtro_archivos_agregar_extension(filtro,"txt");

	/* Se extraen las opciones pasadas como parametro al programa. */
	extraer_opciones(argc, argv, &dir, &dim , filtro, &n_gramas,&stopwords);

	// Abro el archivo de documentos a indexar.
	int cant_archivos = 0;
	vector_t* archivos = dir_obtener_archivos(dir, &cant_archivos);
	vector_t* filtrados = filtro_archivos_filtrar(filtro, archivos, &cant_archivos);

	if (dim > cant_archivos){
		print_warning("La dimension no puede ser mayor a la cantidad de archivos, se utilizara este numero como dimension");
		dim = cant_archivos - 1 ;			
	}

	columnas = parsear_directorio(n_gramas, dir, filtrados, cant_archivos,stopwords);
	log_registrar(COLUMNAS_LOG,columnas);


	// Realizo el merge de los subarchivos generados.	   
	buffer_merge = merge_dos_fases(MERGEFILE,columnas);
	filas = buffer_obtener_procesados(buffer_merge);
	elementos_distintos_de_0 = buffer_obtener_distintos_de_cero(buffer_merge);
	buffer_destruir(buffer_merge);
	log_registrar(FILAS_LOG,filas);
	log_registrar(ELEMENTOS_LOG,elementos_distintos_de_0);
		

	// Genero, a partir de los datos obtenidos en el merge, el archivo matriz en formato st.
	generar_archivo_matriz(filas, columnas, elementos_distintos_de_0);
	

	// Realizo el calculo de la DVS.
	calculo_dvs(dim);
	system("rm *.subarch");
	system("rm output*");



	filtro_archivos_destruir(filtro);

	
	printf("Proceso finalizado con exito.\n");

	return 0;

}
